#include "mainwindow.h"
#include "ui_mainwindow.h"

#include <QModbusRtuSerialSlave>
#include <QDebug>

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);
    setWindowFlags(windowFlags()& ~Qt::WindowMaximizeButtonHint);
    setFixedSize(this->width(), this->height());
    this->setWindowTitle("ModbusSlave");

    InitialSetting();
    InitialInterfaceariables();
    InitialSlaveDatas();
}

MainWindow::~MainWindow()
{
    delete ui;
}

//初始化配置
void MainWindow::InitialSetting()
{
    //填充串口号组合框
    SearchSerialPorts();

    //填充串口波特率
    QStringList baud={"4800","9600","14400","19200","28800","38400","56000","57600","115200","128000","230400","256000"};
    ui->comboBoxBaud->addItems(baud);
    ui->comboBoxBaud->setCurrentIndex(1);

    //填充串口数据位
    ui->comboBoxData->addItem("7位");
    ui->comboBoxData->addItem("8位");
    ui->comboBoxData->addItem("9位");
    ui->comboBoxData->setCurrentIndex(1);

    //填充串口校验位
    ui->comboBoxParity->addItem("无校验");
    ui->comboBoxParity->addItem("奇校验");
    ui->comboBoxParity->addItem("偶校验");

    //填充串口停止位
    ui->comboBoxStop->addItem("1位");
    ui->comboBoxStop->addItem("1.5位");
    ui->comboBoxStop->addItem("2位");

    modbusDevice = new QModbusRtuSerialSlave(this);
}

//搜索串口
void MainWindow::SearchSerialPorts()
{
    ui->comboBoxPort->clear();

    foreach(const QSerialPortInfo &info,QSerialPortInfo::availablePorts())
    {
        ui->comboBoxPort->addItem(info.portName());
    }
}

//初始化从站数据
void MainWindow::InitialSlaveDatas()
{
    if (!modbusDevice)
        return;

    QModbusDataUnitMap reg;
    reg.insert(QModbusDataUnit::Coils, { QModbusDataUnit::Coils, 0, 16 });
    reg.insert(QModbusDataUnit::DiscreteInputs, { QModbusDataUnit::DiscreteInputs, 0, 16 });
    reg.insert(QModbusDataUnit::InputRegisters, { QModbusDataUnit::InputRegisters, 0, 16 });
    reg.insert(QModbusDataUnit::HoldingRegisters, { QModbusDataUnit::HoldingRegisters, 0, 16 });

    modbusDevice->setMap(reg);

    connect(modbusDevice, &QModbusServer::dataWritten,this, &MainWindow::UpdateDisplayBySlave);
    connect(modbusDevice, &QModbusServer::stateChanged,this, &MainWindow::SlaveStateChanged);
    connect(modbusDevice, &QModbusServer::errorOccurred,this, &MainWindow::HandleDeviceError);

    for (quint16 i = 0; i < coilButtons.buttons().count(); ++i)
    {
        modbusDevice->setData(QModbusDataUnit::Coils, i, coilButtons.button(i)->isChecked());
    }

    for (quint16 i = 0; i < discreteButtons.buttons().count(); ++i)
    {
        modbusDevice->setData(QModbusDataUnit::DiscreteInputs, i,discreteButtons.button(i)->isChecked());
    }

    bool ok;
    for (QLineEdit *widget : qAsConst(inputRegisters))
    {
        modbusDevice->setData(QModbusDataUnit::InputRegisters, quint16(widget->property("ID").toUInt()),widget->text().toUShort(&ok, 16));
    }

    for (QLineEdit *widget : qAsConst(holdingRegisters))
    {
        modbusDevice->setData(QModbusDataUnit::HoldingRegisters, quint16(widget->property("ID").toUInt()),widget->text().toUShort(&ok, 16));
    }
}

//根据从站的变化更新数据显示
void MainWindow::UpdateDisplayBySlave(QModbusDataUnit::RegisterType table, int address, int size)
{
    qDebug()<<"table:"<<table<<"address:"<<address<<"size:"<<size<<endl;

    for (int i = 0; i < size; ++i)
    {
        quint16 value;
        QString text;
        switch (table)
        {
        case QModbusDataUnit::Coils:
        {
            modbusDevice->data(QModbusDataUnit::Coils, quint16(address + i), &value);
            coilButtons.button(address + i)->setChecked(value);
            break;
        }
        case QModbusDataUnit::HoldingRegisters:
        {
            modbusDevice->data(QModbusDataUnit::HoldingRegisters, quint16(address + i), &value);
            qDebug()<<value;
            holdingRegisters.value(QStringLiteral("holdReg%1").arg(address + i))->setText(text.setNum(value, 16));
            break;
        }
        default:
            break;
        }
    }
}

//设备错误处理
void MainWindow::HandleDeviceError(QModbusDevice::Error newError)
{
    if (newError == QModbusDevice::NoError || !modbusDevice)
        return;

    statusBar()->showMessage(modbusDevice->errorString(), 5000);
}

//从站状态改变响应
void MainWindow::SlaveStateChanged(int state)
{
    bool connected = (state != QModbusDevice::UnconnectedState);
    ui->pushButtonConnect->setEnabled(!connected);
    ui->pushButtonDisconnect->setEnabled(connected);
}

//初始化设定寄存器对应的界面变量
void MainWindow::InitialInterfaceariables()
{
    coilButtons.setExclusive(false);
    discreteButtons.setExclusive(false);

    QRegularExpression regexp(QStringLiteral("coil(?<ID>\\d+)"));
    const QList<QCheckBox *> coils = findChildren<QCheckBox *>(regexp);
    for (QCheckBox *cbx : coils)
        coilButtons.addButton(cbx, regexp.match(cbx->objectName()).captured("ID").toInt());
    connect(&coilButtons, SIGNAL(buttonClicked(int)), this, SLOT(coilChanged(int)));

    regexp.setPattern(QStringLiteral("state(?<ID>\\d+)"));
    const QList<QCheckBox *> discs = findChildren<QCheckBox *>(regexp);
    for (QCheckBox *cbx : discs)
        discreteButtons.addButton(cbx, regexp.match(cbx->objectName()).captured("ID").toInt());
    connect(&discreteButtons, SIGNAL(buttonClicked(int)), this, SLOT(discreteInputChanged(int)));

    regexp.setPattern(QLatin1String("inReg(?<ID>\\d+)"));
    const QList<QLineEdit *> inreg = findChildren<QLineEdit *>(regexp);
    qDebug()<<inreg.size();
    for (QLineEdit *lineEdit : inreg) {
        inputRegisters.insert(lineEdit->objectName(), lineEdit);
        lineEdit->setProperty("ID", regexp.match(lineEdit->objectName()).captured("ID").toInt());
        lineEdit->setValidator(new QRegularExpressionValidator(QRegularExpression(QStringLiteral("[0-9a-f]{0,4}"),
            QRegularExpression::CaseInsensitiveOption), this));
        connect(lineEdit, &QLineEdit::textChanged, this, &MainWindow::setInputRegister);
    }

    regexp.setPattern(QLatin1String("holdReg(?<ID>\\d+)"));
    const QList<QLineEdit *> holdreg = findChildren<QLineEdit *>(regexp);
    qDebug()<<holdreg.size();
    for (QLineEdit *lineEdit : holdreg) {
        holdingRegisters.insert(lineEdit->objectName(), lineEdit);
        lineEdit->setProperty("ID", regexp.match(lineEdit->objectName()).captured("ID").toInt());
        lineEdit->setValidator(new QRegularExpressionValidator(QRegularExpression(QStringLiteral("[0-9a-f]{0,4}"),
            QRegularExpression::CaseInsensitiveOption), this));
        connect(lineEdit, &QLineEdit::textChanged, this, &MainWindow::setHoldingRegister);
    }
}

void MainWindow::coilChanged(int id)
{
    if(!modbusDevice)
    {
        return;
    }

    QAbstractButton *button = coilButtons.button(id);

    if (!modbusDevice->setData(QModbusDataUnit::Coils, quint16(id), button->isChecked()))
    {
        statusBar()->showMessage(tr("Could not set data: ") + modbusDevice->errorString(), 5000);
    }
}

void MainWindow::discreteInputChanged(int id)
{
    if(!modbusDevice)
    {
        return;
    }

    QAbstractButton *button = discreteButtons.button(id);

    if (!modbusDevice->setData(QModbusDataUnit::DiscreteInputs, quint16(id), button->isChecked()))
    {
        statusBar()->showMessage(tr("Could not set data: ") + modbusDevice->errorString(), 5000);
    }
}

void MainWindow::setInputRegister(const QString &value)
{
    if(!modbusDevice)
    {
        return;
    }

    const QString objectName = QObject::sender()->objectName();

    if(inputRegisters.contains(objectName))
    {
        bool ok = true;
        const quint16 id = quint16(QObject::sender()->property("ID").toUInt());

        if (objectName.startsWith(QStringLiteral("inReg")))
        {
            if(!modbusDevice->setData(QModbusDataUnit::InputRegisters, id, value.toUShort(&ok, 16)))
            {
                statusBar()->showMessage(tr("Could not set register: ") + modbusDevice->errorString(),5000);
            }
        }
    }
}

void MainWindow::setHoldingRegister(const QString &value)
{
    if(!modbusDevice)
    {
        return;
    }

    const QString objectName = QObject::sender()->objectName();

    if(holdingRegisters.contains(objectName))
    {
        bool ok = true;
        const quint16 id = quint16(QObject::sender()->property("ID").toUInt());
        qDebug()<<id;
        if (objectName.startsWith(QStringLiteral("holdReg")))
        {
            if(!modbusDevice->setData(QModbusDataUnit::HoldingRegisters, id, value.toUShort(&ok, 16)))
            {
                statusBar()->showMessage(tr("Could not set register: ") + modbusDevice->errorString(),5000);
            }
        }
    }
}


void MainWindow::on_pushButtonConnect_clicked()
{
    bool intendToConnect = (modbusDevice->state() == QModbusDevice::UnconnectedState);

    statusBar()->clearMessage();

    if (intendToConnect)
    {
        modbusDevice->setConnectionParameter(QModbusDevice::SerialPortNameParameter,ui->comboBoxPort->currentText());
        modbusDevice->setConnectionParameter(QModbusDevice::SerialParityParameter,QSerialPort::NoParity);
        modbusDevice->setConnectionParameter(QModbusDevice::SerialBaudRateParameter,ui->comboBoxBaud->currentText().toInt());
        modbusDevice->setConnectionParameter(QModbusDevice::SerialDataBitsParameter,QSerialPort::Data8);
        modbusDevice->setConnectionParameter(QModbusDevice::SerialStopBitsParameter,QSerialPort::OneStop);

        modbusDevice->setServerAddress(ui->spinBoxStation->text().toInt());
        if (!modbusDevice->connectDevice())
        {
            statusBar()->showMessage(tr("连接失败: ") + modbusDevice->errorString(), 5000);
        }
        else
        {
            ui->pushButtonConnect->setEnabled(false);
            ui->pushButtonDisconnect->setEnabled(true);
        }
    }
}

void MainWindow::on_pushButtonDisconnect_clicked()
{
    modbusDevice->disconnectDevice();
    ui->pushButtonConnect->setEnabled(true);
    ui->pushButtonDisconnect->setEnabled(false);
}
